package com.huawei.cloudapp.model.management;

import static com.huawei.cloudapp.utils.CasCommonUtils.getManagementUrl;
import static com.huawei.cloudapp.utils.CasConstantsUtil.ALLOCATION;
import static com.huawei.cloudapp.utils.CasConstantsUtil.AVAILABLE_PLAYTIME;
import static com.huawei.cloudapp.utils.CasConstantsUtil.BACKGROUND_TIMEOUT;
import static com.huawei.cloudapp.utils.CasConstantsUtil.CLIENT_TYPE;
import static com.huawei.cloudapp.utils.CasConstantsUtil.CLOUD_PHONE_ID;
import static com.huawei.cloudapp.utils.CasConstantsUtil.CONNECTION;
import static com.huawei.cloudapp.utils.CasConstantsUtil.ERROR_CODE;
import static com.huawei.cloudapp.utils.CasConstantsUtil.ERROR_MSG;
import static com.huawei.cloudapp.utils.CasConstantsUtil.IP;
import static com.huawei.cloudapp.utils.CasConstantsUtil.IP_PATTERN;
import static com.huawei.cloudapp.utils.CasConstantsUtil.PHONE_ID;
import static com.huawei.cloudapp.utils.CasConstantsUtil.PORT;
import static com.huawei.cloudapp.utils.CasConstantsUtil.POSITIVE_NUMBER_PATTERN;
import static com.huawei.cloudapp.utils.CasConstantsUtil.SESSION_ID;
import static com.huawei.cloudapp.utils.CasConstantsUtil.TOKEN;
import static com.huawei.cloudapp.utils.CasConstantsUtil.TOUCH_TIMEOUT;

import android.util.Log;

import androidx.annotation.NonNull;

import com.google.gson.Gson;
import com.huawei.cloudapp.common.CASLog;
import com.huawei.cloudapp.model.IConnectInfoModel;
import com.huawei.cloudapp.model.OnRequestListener;
import com.huawei.cloudapp.model.bean.CustomException;
import com.huawei.cloudapp.model.bean.User;
import com.huawei.cloudapp.model.bean.management.CasConnectInfo;
import com.huawei.cloudapp.utils.CasCommonUtils;
import com.huawei.cloudapp.utils.CasHttpUtils;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;

public class ConnectInfoModel implements IConnectInfoModel {

    public static final String TAG = "ConnectInfoModel";

    @Override
    public void getConnectInfo(User user, String phoneId, String region, OnRequestListener<CasConnectInfo> onRequestListener) {
        String url = getManagementUrl() + "/" + CONNECTION + "/" + ALLOCATION;
        HashMap<String, String> requestBody = new HashMap<String, String>();
        requestBody.put(PHONE_ID, phoneId);
        requestBody.put(CLIENT_TYPE, "ANDROID");
        Gson gson = new Gson();
        HashMap<String, String> requestHeader = new HashMap<String, String>();
        requestHeader.put(TOKEN, user.getUserTokenByRegion(region));
        requestHeader.put(SESSION_ID, user.getUserSessionId());
        CasHttpUtils.post(url, requestHeader, gson.toJson(requestBody), new Callback() {
            @Override
            public void onFailure(@NonNull Call call, @NonNull IOException e) {
                onRequestListener.onFailure(e);
            }

            @Override
            public void onResponse(@NonNull Call call, @NonNull Response response) {
                try {
                    onRequestListener.onSuccess(parseResponseToConnectInfo(response), 1);
                } catch (Exception e) {
                    onRequestListener.onFailure(e);
                }
            }
        });
    }

    @Override
    public List<CasConnectInfo> parseResponseToConnectInfo(Response response) throws Exception {
        if (response == null) {
            Log.e(TAG, "Failed to get response from server.");
            throw new CustomException.EmptyResponseFromServerException();
        }

        String rspStr = response.body().string();
        if (rspStr.isEmpty()) {
            Log.e(TAG, "Failed to get start param response.");
            throw new CustomException.EmptyResponseFromServerException();
        }
        Map<String, Object> rspMap = CasCommonUtils.parseJsonMessage(rspStr);
        if (rspMap.isEmpty()) {
            Log.e(TAG, "Failed to get start param response, rspMap is empty.");
            throw new CustomException.EmptyResponseFromServerException();
        }
        if (rspMap.containsKey(ERROR_CODE)) {
            if (rspMap.get(ERROR_CODE).equals("CPH.MANAGER.USER.00003")) {
                Log.e(TAG, "The session is invalid, need to log in again.");
                throw new CustomException.LogInInfoInvalidException();
            } else {
                Log.e(TAG, "Failed to get start param response. " + rspMap.get(ERROR_CODE) + ": " + rspMap.get(ERROR_MSG));
                throw new CustomException.FailedToGetConnectInfoException(rspMap.get(ERROR_CODE) + ": " + rspMap.get(ERROR_MSG));
            }
        }

        if (!rspMap.containsKey(CLOUD_PHONE_ID) || !CasCommonUtils.isValidUUID((String) rspMap.get(CLOUD_PHONE_ID))) {
            Log.e(TAG, "The cloud_phone_id is invalid.");
            throw new CustomException.ResponseParamsException();
        }
        String cloudPhoneId = (String) rspMap.get(CLOUD_PHONE_ID);

        if (!rspMap.containsKey(IP) || !rspMap.containsKey(PORT) || !rspMap.containsKey(SESSION_ID)) {
            Log.e(TAG, "The start param is invalid.");
            throw new CustomException.ResponseParamsException();
        }

        String ip = (String) rspMap.get(IP);
        if (ip == null || !IP_PATTERN.matcher(ip).matches()) {
            CASLog.e(TAG, "Parameter: ip is invalid.");
            throw new CustomException.ResponseParamsException();
        }

        String port = String.valueOf(((Double) rspMap.get(PORT)).intValue());
        if (port.isEmpty() || !POSITIVE_NUMBER_PATTERN.matcher(port).matches()) {
            CASLog.e(TAG, "Parameter: port is invalid.");
            throw new CustomException.ResponseParamsException();
        }

        String sessionId = (String) rspMap.get(SESSION_ID);
        if (sessionId == null) {
            CASLog.e(TAG, "Parameter: session_id is invalid.");
            throw new CustomException.ResponseParamsException();
        }

        String backgroundTimeout = String.valueOf(((Double) rspMap.get(BACKGROUND_TIMEOUT)).intValue());
        if (backgroundTimeout.isEmpty()) {
            CASLog.e(TAG, "Parameter: backgroundTimeout is invalid.");
            throw new CustomException.ResponseParamsException();
        }

        String availablePlaytime = String.valueOf(((Double) rspMap.get(AVAILABLE_PLAYTIME)).intValue());
        if (availablePlaytime.isEmpty()) {
            CASLog.e(TAG, "Parameter: available_playtime is invalid.");
            throw new CustomException.ResponseParamsException();
        }

        String touchTimeout = String.valueOf(((Double) rspMap.get(TOUCH_TIMEOUT)).intValue());
        if (touchTimeout.isEmpty()) {
            CASLog.e(TAG, "Parameter: touch_timeout is invalid.");
            throw new CustomException.ResponseParamsException();
        }

        CasConnectInfo mConnectInfo = GenerateConnectInfo(cloudPhoneId, ip, port, sessionId, backgroundTimeout, availablePlaytime, touchTimeout);
        return Collections.singletonList(mConnectInfo);
    }

    public CasConnectInfo GenerateConnectInfo(String cloudPhoneId,
                                              String ip,
                                              String port,
                                              String sessionId,
                                              String backgroundTimeout,
                                              String availablePlayTime,
                                              String touchTimeout) {
        if (ip.isEmpty() || port.isEmpty()) {
            return null;
        }
        if (sessionId.isEmpty()) {
            sessionId = UUID.randomUUID().toString().replaceAll("-", "");
        }
        CasConnectInfo connectorInfo = new CasConnectInfo();
        connectorInfo.setConnectIp(ip);
        connectorInfo.setConnectPort(port);
        connectorInfo.setBackgroundTimeout(backgroundTimeout.isEmpty() ? "3600" : backgroundTimeout);
        connectorInfo.setAvailablePlayTime(availablePlayTime.isEmpty() ? "0" : availablePlayTime);
        connectorInfo.setSessionId(sessionId);
        connectorInfo.setTicket("ticket_xxxxxx");
        connectorInfo.setAesKey(cloudPhoneId);
        connectorInfo.setAuthTs(String.valueOf(System.currentTimeMillis()));
        connectorInfo.setTouchTimeout(touchTimeout.isEmpty() ? "0" : touchTimeout);
        return connectorInfo;
    }
}
